﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DirectX = Microsoft.DirectX.DirectSound;
using System.IO;
using NAudio.Wave;
using System.Threading;
using System.Windows.Forms;
using AKVAsmart.AFS.VPA.Entities;

/*
 * SoundPlayer Player = new SoundPlayer();
 * MME stands for MultiMedia Extension WinMM
 * https://winmm.codeplex.com/ WinMM.Net is a .NET style wrapper for the winmm.dll functionality. 
 * 
 * Setting a Cooperative Level
 * https://sites.google.com/site/craigandera/craigs-stuff/directx-home/managed-directsound-tutorial/01-directsound-basics
 * 
 * http://hasuite.googlecode.com/svn/trunk/NAudio/Wave/WaveStreams/WaveFileReader.cs
 * http://stackoverflow.com/questions/4997360/directsound-timing-and-sample-count
 * 
 * 
 * 
 * 
 * 
 * 
 * */

namespace micwav
{
	class Program
	{
		static void Main(string[] args)
		{
			var p = new Program();
			p.Go();

		}

		//float Limit = 0.4f;
		//float negLimit = -0.4f;

		StreamWriter pFs;

		private void Go()
		{
			DoReadFile();
			return;
			
			var mic = GetMic("mic", "usb");

			var waveFormat = new DirectX.WaveFormat();
			
			waveFormat.BitsPerSample = 16;
			//waveFormat.BlockAlign = 2;
			waveFormat.Channels = 1;
			waveFormat.FormatTag = DirectX.WaveFormatTag.Pcm;
			//should be a common value such as 8.0 kHz, 11.025 kHz, 22.05 kHz, or 44.1 kHz.
			waveFormat.SamplesPerSecond = 44100;// 22050;

				//            case 0: format.SamplesPerSecond = 22050; break;
				//case 1: format.SamplesPerSecond = 44100; break;
				//            case 3: format.SamplesPerSecond = 96000; break;
				//case 2: format.SamplesPerSecond = 208200; break;


			//waveFormat.AverageBytesPerSecond = 44100 * 2;

			//NotifySize = (1024 > waveFormat.AverageBytesPerSecond / 8) ? 1024 : (waveFormat.AverageBytesPerSecond / 8);
		//	NotifySize -= NotifySize % waveFormat.BlockAlign;

			waveFormat.BlockAlign = (short)(waveFormat.Channels * waveFormat.BitsPerSample / 8);
			waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * waveFormat.SamplesPerSecond;

		

			var capDesc = new DirectX.CaptureBufferDescription();

			int CaptureBuffSize = 1000000;
			capDesc.BufferBytes = CaptureBuffSize;
			capDesc.WaveMapped = false;
			capDesc.ControlEffects = false;
			capDesc.Format = waveFormat;


			var capBuff = new DirectX.CaptureBuffer(capDesc, mic);
			

			Console.WriteLine("Press to rec");
			Console.ReadKey();
			Console.WriteLine("rec.. press to stop");
			capBuff.Start(false);
			Console.ReadKey();
			capBuff.Stop();
			Console.WriteLine("stopped");

			int readPos = 0, capturePos;
			capBuff.GetCurrentPosition(out capturePos, out readPos);
			byte[] capturedData = (byte[])capBuff.Read(0, typeof(byte), DirectX.LockFlag.None, capturePos);//, lockSize);

			

			short[] singles = GetSampleShorts(capturedData);
			float[] floats = GetSampleFloats(singles);
			bool[] bools = GetSampleBools(singles);


			
			//Console.Clear();

			Detector(bools, waveFormat);
		}

		private void DoReadFile()
		{
			//@"c:\temp\_wav\25.01.2015 13.27.01.wav")) 
			//@"c:\temp\_wav\25.01.2015 13.27.01.wav")
			using (NAudio.Wave.WaveFileReader aa = new WaveFileReader(@"D:\Mine Dokumenter\rf433\PT2260 knapp A lang trykk bra signal.wav"))
//@"D:\Mine Dokumenter\rf433\EV1527 2 trykk B bra utdrag.wav"))
//
//jobbgarange bra signal extra sinelce front.wav"))
				//jobbgarange ny som viser 5 korte og 4 lange.wav"))
//@"c:\temp\_wav\25.01.2015 13.27.01.wav"))
//@"D:\Mine Dokumenter\rf433\EV1527 2 trykk B bra utdrag.wav"))
//jobbgarange ny som viser 5 korte og 4 lange.wav"))
//EV1527 2 trykk B bra utdrag.wav"))
				//jobbgarange bra signal extra sinelce front.wav"))
//PT2260 knapp A lang trykk bra signal.wav"))
//EV1527 2 trykk B bra utdrag.wav"))
//jobbgarange bra signal extra sinelce front.wav"))//@"D:\Mine Dokumenter\rf433\EV1527 2 trykk A.wav"))
//))//jobbgarange bra signal extra sinelce front.wav"))
			{
				if (aa.WaveFormat.Channels != 1 || aa.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
					aa.WaveFormat.BitsPerSample != 16 && aa.WaveFormat.SampleRate != 441000)
					throw new Exception("wav not suppoeted");

				int i = 0;
				float[] aas = new float[aa.SampleCount];
				while (true)
				{
					float[] flo = aa.ReadNextSampleFrame();
					if (flo == null)
						break;
					else if (flo.Length != 1)
						throw new Exception("ee");
					else
						aas[i++] = flo[0];
				}

				Detector(GetSampleBools(aas), ConvertWaveFormat(aa.WaveFormat));
			}
		}

		private DirectX.WaveFormat ConvertWaveFormat(WaveFormat waveFormat)
		{
			DirectX.WaveFormat wf = new DirectX.WaveFormat();
			wf.AverageBytesPerSecond = waveFormat.AverageBytesPerSecond;
			wf.BitsPerSample = (short)waveFormat.BitsPerSample;
			wf.BlockAlign = (short)waveFormat.BlockAlign;
			wf.Channels = (short)waveFormat.Channels;
			if (waveFormat.Encoding == WaveFormatEncoding.Pcm)
				wf.FormatTag = DirectX.WaveFormatTag.Pcm;
			else
				throw new Exception("only support pcm");
			wf.SamplesPerSecond = waveFormat.SampleRate;

			return wf;
		}

		public void Detector(bool[] bools, DirectX.WaveFormat waveFormat)
		{
			List<Samples> samples = new List<Samples>();

			int minLowsBeforeHighTrigger = 200; // 200 = 4540us

			int lowsBeforeHigh = 0;
			//int start = 0;
			for (int u = 0; u < bools.Length; u++)
			{
				if (!bools[u])
					lowsBeforeHigh++;
				else if (lowsBeforeHigh < minLowsBeforeHighTrigger)
					lowsBeforeHigh = 0;
				else // high after > 200 lows
				{
					// find highs before the long low
					//int bbb = u - lows;
					//int higsBeforeLows = 0;
					//while (bools[--bbb]) higsBeforeLows++;
					//Log(string.Format("Highs before lows {0}, {1}us", higsBeforeLows, Math.Round(higsBeforeLows * uSecPerSample)));
					int start = u;

//					samples.Add(new Sample(false, lows, u));

					Log(string.Format("H after {0} lows, Pos {1}, {2}us", lowsBeforeHigh, u, Math.Round(lowsBeforeHigh * GetuSecPerSample(waveFormat))));

					bool bb = bools[u];
					while (u < bools.Length)
					{
						int x = 0;
						int ush = u;
						while (u < bools.Length && bools[u++] == bb) x++;
						u--; // we went one too much to figure out we were done. go back one.
						if (u >= bools.Length)
						{
							Console.WriteLine("REACHED END OF DATA. Pos " + u);
							goto outt;
						}
						if (x >= 1000)
						{
							Console.WriteLine("Over 1000 samples. Pos " + u);
							goto outt;
						}
						if (!bb && x > minLowsBeforeHighTrigger) // done
						{
	//						int end = u - x - 1; //end = last frame before going low for longer time
//							int len = end - start + 1;

							AveragerGui(lowsBeforeHigh, samples, waveFormat);

							BruteForcePulseDetector(samples);

							Play(lowsBeforeHigh, samples, waveFormat);
							
							
							
						}

						// TODO: nedre grense for samples oxo?

						Log(string.Format("{0} for {1} samples. Pos {2}, {3}us", bb ? "H" : "L", x, u, Math.Round(x * GetuSecPerSample(waveFormat))));

						samples.Add(new Samples(bb, x));

						bb = !bb;
					}

					
				}

		
			}
		outt: ;

		

		}

		private void Play(int lowsBeforeHigh, List<Samples> samples, DirectX.WaveFormat waveFormat)
		{
			byte[] pcm = GeneratePcm(true, 0, lowsBeforeHigh, 3, samples);

			MemoryStream msWav = new MemoryStream();
			WriteWavHeader(msWav, waveFormat, pcm.Length);
			msWav.Write(pcm, 0, pcm.Length);
			msWav.Position = 0;

			File.WriteAllBytes(Path.Combine(@"C:\temp\_wav", DateTime.Now.ToString("dd.MM.yyyy HH.mm.ss") + ".wav"), msWav.GetBuffer());

			PlayWav(msWav, waveFormat);
		}

		class Candi : IComparable<Candi>
		{
			public double Diff = -1;
			public double HiuSec;
			public double LouSec;
			public  double testuSec;
			public  double bruteForceTotuSec;
			public double RatDiffExp;

			public Candi(double testuSec, double bruteForceTotuSec, double HiuSec, double LouSec,  double totusecdiff, double ratDiffExp)
			{
				// TODO: Complete member initialization
				this.testuSec = testuSec;
				this.bruteForceTotuSec = bruteForceTotuSec;
				this.Diff = totusecdiff;
				this.HiuSec = HiuSec;
				this.LouSec = LouSec;
				this.RatDiffExp = ratDiffExp;
			}

			private double CompDiff
			{
				get
				{
					return Math.Abs(Diff);// +Math.Abs(RatDiffExp);// +Math.Abs(HiDiff) + Math.Abs(LoDiff);
				}
			}

			public int CompareTo(Candi other)
			{
				return this.CompDiff.CompareTo(other.CompDiff);
			}
		}

		private void BruteForcePulseDetector(List<Samples> samples)
		{
			foreach (var s in samples)
				if (s.AvgSamples == -1)
					s.AvgSamples = s.SampleCount;

			int totSamples = 0;
			

			foreach (var s in samples)
			{
				totSamples += s.SampleCount;

				double dura = s.SampleCount * uSecPerSample;
			}

			double totuSec = totSamples * uSecPerSample;
			
				

			List<Candi> candi = new List<Candi>();

			// brute force
			for (double testuSec = 200d; testuSec < 500d; testuSec++)
			{
				double bruteForceTotuSec = 0d;
				double bruteForceLowuSec = 0d;
				double bruteForceHighuSec = 0d;

				double hiPulses = 0;
				double loPulses = 0;

				foreach (var s in samples)
				{
					int roundPulses = GetRoundedPulses(s, testuSec);

					double dura = testuSec * roundPulses;

					bruteForceTotuSec += dura;

					if (s.Value)
					{
						bruteForceHighuSec += dura;
						hiPulses += s.AvgSamplesInt;
					}
					else
					{
						bruteForceLowuSec += dura;
						loPulses += s.AvgSamplesInt;
					}

				}

				double avgSampRat = hiPulses / loPulses; //constant!!

				double diffRat = bruteForceHighuSec / bruteForceLowuSec;// diff.HiuSec / diff.LouSec;

				double ratDiff = (avgSampRat - diffRat) * 10000d;

				candi.Add(new Candi(testuSec, bruteForceTotuSec, bruteForceHighuSec, bruteForceLowuSec,
					totuSec - bruteForceTotuSec,
					ratDiff
					));

			}

			candi.Sort();

			foreach (Candi diff in candi)
			{
				Log("Candidate uSec pulse " + diff.testuSec);
				double totaluSecNew = 0d;



				foreach (var s in samples)
				{
					int roundPulses = GetRoundedPulses(s, diff.testuSec);
					totaluSecNew += diff.testuSec * roundPulses;

	

					Log(string.Format("{0} for {1} AvgSamplesInt. {2} SampleCount, {3} RoundPulses ", s.Value ? "H" : "L", s.AvgSamplesInt, s.SampleCount, roundPulses));
				}
				Log("Total usec org " + totuSec);
				Log("Total usec new " + totaluSecNew);

				



				Log(string.Format("Diff {0},  This hi/lo rat. {1}, avgSampRat {2}, ratDiff {3}",
					diff.Diff,
					-1,//diffRat,
					-1,//avgSampRat,
					diff.RatDiffExp));

				Console.ReadKey();
			}
		}

		private int GetRoundedPulses(Samples s, double testuSec)
		{
			double uSec = s.AvgSamples * uSecPerSample;


			double pulses = uSec / testuSec;

			int roundPulses = (int)Math.Round(pulses);
			if (roundPulses <= 0)
				roundPulses = 1;
			return roundPulses;
		}

		private void AveragerGui(int lowsBeforeHigh, List<Samples> samples, DirectX.WaveFormat waveFormat)
		{

			//int hh = 3, ll = 3;
			int all = 3;
			bool again = true;
//			bool highAndLowTogether = false;
			while (again)
			{

				Average(samples, ref all, waveFormat);

				Log(string.Format("Average {0}", all));//ll, hh, highAndLowTogether));
				Log("H to reduce highs, L to reduce lows, H+ to add highs, L+ to add lows, M to toggle highAndLowTogether (merge). Then ENTER. Or just ENTER to continue");

				var ki = Console.ReadLine();
				if (ki.StartsWith("+", StringComparison.OrdinalIgnoreCase))
					all++;
	//			else if (ki.StartsWith("L+", StringComparison.OrdinalIgnoreCase))
//					ll++;
				else if (ki.StartsWith("-", StringComparison.OrdinalIgnoreCase))
				{
					all--;
					if (all <= 0)
						all = 1;
				}
				//else if (ki.StartsWith("L", StringComparison.OrdinalIgnoreCase))
				//{
				//    ll--;
				//    if (ll <= 0)
				//        ll = 1;
				//}
				//else if (ki.StartsWith("M", StringComparison.OrdinalIgnoreCase))
				//    highAndLowTogether = !highAndLowTogether;
				else
					again = false;
			}



			//		samples.Add(new Sample(false, -1, u)); // -1 = fake end




			
		}

		private bool[] GetSampleBools(List<Samples> samples)
		{
			List<bool> bools = new List<bool>();
			
			//int len = 0;
			//foreach (var sax in samples)
			//    len += sax.AvgSamplesInt;

			////// Offset into new array starting at index = 0
			//bool[] code = new bool[len];
			////Array.Copy(bools, start, code, 0, len);
			//////int h = 0;
			//////for (int y = start; y <= end; y++)
			//////    code[h++] = bools[y];
			//////int lowBefore = lows;

			////code = new bool[newLen];
			//int c = 0;
			foreach (var sa in samples)
			{
				int sample = sa.AvgSamplesInt;
				while (sample-- > 0)
					bools.Add(sa.Value);
			}

			return bools.ToArray();
		}

		private double uSecPerSample = 1000000d / 44100;//waveFormat.SamplesPerSecond;

		private double GetuSecPerSample(DirectX.WaveFormat waveFormat)
		{
			return 1000000d / waveFormat.SamplesPerSecond;
			//1000000 / 44100 = 22.6757369615 us
		}

		private void Log(string p)
		{
			Console.WriteLine(p);
			
			if (pFs == null)
				pFs = new StreamWriter(Path.Combine(@"C:\temp\_wav", DateTime.Now.ToString("dd.MM.yyyy HH.mm.ss") + ".txt"));

			pFs.WriteLine(p);
			pFs.Flush();
		}

		//class GenResult
		//{
		//    public List<int> LowDurs = new List<int>();
		//    public List<int> HighDurs = new List<int>();
		//}

		// New plan for Generalize: crystal in sender get more incorrect as time goes. Trust earlier values best.
		/// <summary>
		/// Logic: for all durations of H  will always have max 2 durations: short or long pulse
		/// For all durations of L will we always have 2 durations: long or short
		/// 
		/// PS: the initial LOW need to be special cased
		/// 
		/// <param name="samples"></param>
		private void Average(List<Samples> samples, ref int reduce , DirectX.WaveFormat wf)
		{
			List<GenEntry> all = new List<GenEntry>();
//			List<GenEntry> lows = null;

			//if (highAndLowTogether)
			//    lows = highs;
			//else
			//    lows = new List<GenEntry>();


			foreach (Samples s in samples)
			{
				//List<GenEntry> ht;
				//if (s.Value)
				//    ht = highs;
				//else
				//    ht = lows;

				GenEntry ge = all.Find(a => a.SampleOccurances.ContainsKey(s.SampleCount));
				if (ge == null)
				{
					ge = new GenEntry();
					all.Add(ge);
				}
				ge.SampleOccurances[s.SampleCount]++;
			
			}

			// TODO: kan redusere helt ned til 1 hvis det får lengden til å passe muligens???

			// NY LOGIKK: bare reduser til vi får en total lengde som passer best:-)

			// We want 2 durations for both M and L
			int aa = 1;
			while (all.Count > reduce)
			{
				if (!Reduce(all, aa))
					aa++;
			}
			reduce = all.Count; //REf
#if NOT
			int b = 1;
			while (highs.Count > reduceHighs)
			{
				if (!Reduce(highs, b))
					b++;
			}
			reduceHighs = highs.Count; //Ref
#endif
			Log("Averaged values:");

			//HashSet<GenEntry> calced = new HashSet<GenEntry>();

			int totSamples = 0;
			int totSamplesGen = 0;
			foreach (Samples s in samples)
			{
//				if (s.Value)
	//				CalcOccurance(highs, s);//, calced);
		//		else
			//		CalcOccurance(lows, s);//, calced);

				CalcOccurance(all, s);

				Log(string.Format("{0} for {1} gen. samples.", s.Value ? "H" : "L", s.AvgSamplesInt, s.SampleCount));
	
				totSamples += s.SampleCount;
				totSamplesGen += s.AvgSamplesInt;
			}

			Log(string.Format("TotSamples {0}, TotAvgSamples {1}", totSamples, totSamplesGen));

			//foreach (var ld in lows)
			//    Logg("Low", ld, wf);
			//foreach (var hd in highs)
			//    Logg("High", hd, wf);
			foreach (var hd in all)
				Logg("All", hd, wf);
		}

		private void Logg(string ss, GenEntry ge, DirectX.WaveFormat wf)
		{
			Log(string.Format("{0}: {1} sampls, {2} uSec, {3} occurs", ss, Math.Round(ge.AvgValue), Math.Round(ge.AvgValue * GetuSecPerSample(wf)), ge.SumOccurances));
		}




		private void CalcOccurance(List<GenEntry> ht, Samples s)//, HashSet<GenEntry> calced)
		{

			List<GenEntry> gee = ht.FindAll(a => a.SampleOccurances.ContainsKey(s.SampleCount));
			if (gee.Count != 1)
				throw new Exception("err");
			GenEntry ge = gee[0];
			if (ge.AvgValue == -1)//!calced.Contains(ge))
			{
//				calced.Add(ge);

				//ge.MostOccuredSamples = 0;
				//ge.MaxOccurances = 0;

				double sum = 0d;
				int totCount = 0;

				foreach (var kv in ge.SampleOccurances)
				{
					//if (kv.Value > ge.MaxOccurances)
					//{
					//    ge.MostOccuredSamples = kv.Key;
					//    ge.MaxOccurances = kv.Value;
					//}

					sum += ((double)kv.Key * (double)kv.Value);
					totCount += kv.Value;
				}

				ge.AvgValue = sum / totCount;
			}

			s.AvgSamples = ge.AvgValue;

		}

		private bool Reduce(List<GenEntry> ht, int x)
		{
		    bool mergedSome = false;

			foreach (var ge in new List<GenEntry>(ht))
			{
			
				foreach (int s in new List<int>(ge.SampleOccurances.Keys))
				{
					GenEntry gbefore = ht.Find(a => a.SampleOccurances.ContainsKey(s - x));
					if (gbefore != null && gbefore != ge)
					{
						foreach (var ad in gbefore.SampleOccurances)
							ge.SampleOccurances[ad.Key] += ad.Value;

						gbefore.SampleOccurances.Clear();
						ht.Remove(gbefore);
						mergedSome = true;
					}
					GenEntry gaft = ht.Find(a => a.SampleOccurances.ContainsKey(s + x));
					if (gaft != null && gaft != ge)
					{
						foreach (var ad in gaft.SampleOccurances)
							ge.SampleOccurances[ad.Key] += ad.Value;

						gaft.SampleOccurances.Clear();
						ht.Remove(gaft);
						mergedSome = true;
					}
				}
			}

			return mergedSome;
		}

		class GenEntry
		{
			//public int MostOccuredSamples = -1;
			//public int MaxOccurances = -1;
			public double AvgValue = -1;

			public Hashtable<int, int> SampleOccurances = new Hashtable<int, int>();


			//internal GenEntry Clone()
			//{
			//    GenEntry clone = new GenEntry();
			//    clone.MostOccuredSamples = this.MostOccuredSamples;
			//    clone.MaxOccurances = this.MaxOccurances;
			//    clone.AvgValue = this.AvgValue;
			//    foreach (var kv in SampleOccurances)
			//        clone.SampleOccurances[kv.Key] = kv.Value;
			//    return clone;
			//}

			public int SumOccurances
			{
				get
				{
					int res = 0;
					foreach (var v in SampleOccurances)
						res += v.Value;
					return res;
				}
			}

		}


		class Samples
		{
//			public int BruteForcePulseCount = -1;
			public bool Value; // true = high, false = low
			public int SampleCount;
			public double AvgSamples = -1;

			public int AvgSamplesInt
			{
				get
				{
					return (int)Math.Round(AvgSamples);
				}
			}

			public Samples(bool value, int sampleCount)
			{

				this.Value = value;
				this.SampleCount = sampleCount;
			}


		//	public int RoundPulses = -1;
		}



		//class PCM
		//{
		//    public byte[] Data;
		//    public int RealStart; // first play we want to have a init synch pules, for next plays we weatn to tskip it
		//}

		private void PlayWav(MemoryStream msWav, DirectX.WaveFormat waveFormat)
		{
			

			// Describe the sound buffer we want to create below.
			var waveformBufferDescription = new DirectX.BufferDescription(waveFormat);
			waveformBufferDescription.ControlVolume = true;
			waveformBufferDescription.GlobalFocus = true;
			waveformBufferDescription.ControlEffects = false;
	//		waveformBufferDescription.BufferBytes = (int)ms.Length;// waveFormat.AverageBytesPerSecond;

			DirectX.Device speak = GetSpeaker("speak", "usb");
			DirectX.SecondaryBuffer sb = new DirectX.SecondaryBuffer(msWav, waveformBufferDescription, speak);
			sb.Volume = (int)DirectX.Volume.Max;

			//while (true)
			{
				sb.Play(0, DirectX.BufferPlayFlags.Looping);
				Thread.Sleep(1000);
				sb.Stop();

				//sb.SetCurrentPosition(pcm.;
			}

		}

		private void WriteWavHeader(MemoryStream ms, DirectX.WaveFormat waveFormat, int pcmDataLen)
		{
			short padding = 1;
			int formatLength = 0x10;
			//int length = 0; // fill this in later!
			short shBytesPerSample = 2; // changing the WaveFormat recording parameters will impact on this
			// see referenced blog posts for more details

			var bw = new BinaryWriter(ms);
			bw.Write(new char[] { 'R', 'I', 'F', 'F' });
			bw.Write(pcmDataLen + 36);//length); 36 what??
			bw.Write(new char[] { 'W', 'A', 'V', 'E' });
			bw.Write(new char[] { 'f', 'm', 't', ' ' });
			bw.Write(formatLength);
			bw.Write(padding);
			bw.Write(waveFormat.Channels);
			bw.Write(waveFormat.SamplesPerSecond);
			bw.Write(waveFormat.AverageBytesPerSecond);
			bw.Write(shBytesPerSample);
			bw.Write(waveFormat.BitsPerSample);
			bw.Write(new char[] { 'd', 'a', 't', 'a' });

			bw.Write(pcmDataLen);//(int)0); // update sample later
	
		}


		/// <summary>
		/// TODO: if we play just one cycle, we need a initPos and a tailNeg
		/// example:
		/// 1 time:
		/// _____
		/// </summary>
		private byte[] GeneratePcm(bool startWithSeparator,
			int highsBeforeStartWithSeparator,  // only valid if startWithSeparator = true
			int separatorLows, 
			int repetitons,
			List<Samples> samples)
		{
			if (repetitons < 1)
				throw new Exception("Repetitonas must be > 0");
			if (separatorLows < 200)
				throw new Exception("separatorSamples must be > 200 (typically > 400)");

			MemoryStream ms = new MemoryStream();

			short pos = short.MaxValue;
			short neg = short.MinValue;

			if (startWithSeparator)
			{
				int hibef = highsBeforeStartWithSeparator;
				while (hibef-- > 0)
				{
				    var r = BitConverter.GetBytes(pos);
				    ms.Write(r, 0, r.Length);
				}

				int initNeg = separatorLows;
				while (initNeg-- > 0)
				{
					var r = BitConverter.GetBytes(neg);
					ms.Write(r, 0, r.Length);
				}
			}

			bool[] bools = GetSampleBools(samples);

			int loops = repetitons;
			while (loops-- > 0)
			{
				foreach (bool b in bools)
				{
					var r = BitConverter.GetBytes(b ? pos : neg);
					ms.Write(r, 0, r.Length);
				}

				int sepSam = separatorLows;
				while (sepSam-- > 0)
				{
					var r = BitConverter.GetBytes(neg);
					ms.Write(r, 0, r.Length);
				}
			}

			return ms.GetBuffer();
		}

		private bool[] GetSampleBools(short[] shorts)
		{
			bool[] res = new bool[shorts.Length];

			for (int i = 0; i < shorts.Length; i++)
				res[i] = shorts[i] > 0;
			return res;
		}
		private bool[] GetSampleBools(float[] floats)
		{
			bool[] res = new bool[floats.Length];

			for (int i = 0; i < floats.Length; i++)
				res[i] = floats[i] > 0f;
			return res;
		}

		private short[] GetSampleShorts(byte[] capturedData)//, DirectX.WaveFormat waveFormat)
		{
			int samples = capturedData.Length / 2;
			short[] res = new short[samples];

			int b = 0;
			for (int i = 0; i < capturedData.Length - 1; i += 2)
				res[b++] = BitConverter.ToInt16(capturedData, i);

			return res;
		}

		private float[] GetSampleFloats(short[] shorts)
		{
			float[] res = new float[shorts.Length];

			for (int i = 0; i < shorts.Length; i++)
				res[i] = shorts[i] / 32768f;
			return res;
		}




		private DirectX.Device GetSpeaker(params string[] strings)
		{
			DirectX.DevicesCollection cdc = new DirectX.DevicesCollection();
			for (int i = 0; i < cdc.Count; i++)
			{
				bool hasAllStrings = true;
				foreach (string s in strings)
					if (!cdc[i].Description.ToLower().Contains(s))
					{
						hasAllStrings = false;
						break;
					}

				if (hasAllStrings)
				{
					DirectX.Device ret = new DirectX.Device(cdc[i].DriverGuid);
					ret.SetCooperativeLevel(new Button(), DirectX.CooperativeLevel.Priority);
					return ret;
				}
			}
			return null;
		}

		private DirectX.Capture GetMic(params string[] strings)
		{
			var cdc = new DirectX.CaptureDevicesCollection();
			for (int i = 0; i < cdc.Count; i++)
			{
				bool hasAllStrings = true;
				foreach (string s in strings)
					if (!cdc[i].Description.ToLower().Contains(s))
					{
						hasAllStrings = false;
						break;
					}

				if (hasAllStrings)
					return new DirectX.Capture(cdc[i].DriverGuid);
			}
			return null;
		}
	}

	
}
